package com.lqk.netty.client;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;

import com.lqk.netty.common.protobuf.MessageBuilder;
import com.lqk.netty.common.protobuf.MessageModule;
import com.google.protobuf.GeneratedMessageV3;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;
import io.netty.handler.codec.protobuf.ProtobufEncoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;

/**
 * @author: wyl
 * @date: 2021/7/13
 * @des: netty客户端
 * 不需要spring管理，因为会阻塞springboot启动流程，单独的服务
 * tomcat启动后，启动本Server
 */
public class Client {

    /**
     * 创建线程池
     */
    private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);

    private EventLoopGroup group = new NioEventLoopGroup(2);

    public static final String HOST = "127.0.0.1";

    public static final int PORT = 8888;


    private Channel channel;

    private AtomicBoolean isConnect = new AtomicBoolean(false);

    private static class SingletonHolder {
        private static final Client INSTANCE = new Client();
    }

    public static final Client getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private Client() {
    }

    /**
     * springboot服务启动后，ApplicationListenerReadyEvent中会先调用此方法初始化客户端服务
     */
    public synchronized void init() {
        if (!isConnect.get()) {
            try {
                this.connect(HOST, PORT);
                isConnect.set(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void connect(String host, int port) throws Exception {

        // 配置客户端NIO线程组
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
                            ch.pipeline().addLast(new ProtobufDecoder(MessageModule.Message.getDefaultInstance()));
                            ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
                            ch.pipeline().addLast(new ProtobufEncoder());
                            ch.pipeline().addLast(new ClientHandler());
                        }
                    });
            // 发起异步连接操作
            ChannelFuture future = b.connect(host, port).sync();
            this.channel = future.channel();
            System.out.println("Client Start.. ");
            this.channel.closeFuture().sync();
        } finally {
            // 	所有资源释放完成之后，清空资源，再次发起重连操作
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                        try {
                            connect(host, port);// 发起重连操作
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    /**
     * $sendMessage
     * 发送数据的方法
     *
     * @param module      模块
     * @param cmd         指令
     * @param messageData 数据内容
     */
    public void sendMessage(String module, String cmd, GeneratedMessageV3 messageData) {
        this.channel.writeAndFlush(MessageBuilder.getRequestMessage(module, cmd, messageData));
    }
}
